package app.modules.security.business.service.function;

import app.modules.common.constant.CommonLockConstants;
import app.modules.menu.business.service.IMenuFunctionRelationService;
import app.modules.security.business.service.SyncRbacCacheService;
import app.modules.security.constant.SecurityLockConstants;
import app.modules.security.constant.SecurityMsgConstants;
import app.modules.security.dto.biz.AssignedFunctionDTO;
import app.modules.security.dto.web.function.WebFunctionDTO;
import app.modules.security.dto.web.function.WebFunctionQueryDTO;
import app.modules.security.dto.web.function.WebFunctionSaveDTO;
import app.modules.security.dto.web.function.WebFunctionUpdateDTO;
import app.modules.security.persistence.dao.FunctionDAO;
import app.modules.security.persistence.dao.FunctionPermissionRelationDAO;
import app.modules.security.persistence.dao.FunctionResourceRelationDAO;
import app.modules.security.persistence.dao.RoleFunctionRelationDAO;
import app.modules.security.util.conversion.FunctionConvertUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import jasmine.framework.common.exception.BusinessException;
import jasmine.framework.common.exception.DataNotFoundException;
import jasmine.framework.common.util.CheckUtil;
import jasmine.framework.common.util.MapperUtil;
import jasmine.framework.common.util.NewUtil;
import jasmine.framework.lock.annotation.DistributedLock;
import jasmine.security.rbac.model.SecFunction;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Collection;
import java.util.Collections;
import java.util.List;

/**
 * @author mh.z
 */
@Service
public class FunctionService implements IFunctionService {
    private FunctionDAO functionDAO;
    private RoleFunctionRelationDAO roleFunctionRelationDAO;
    private FunctionPermissionRelationDAO functionPermissionRelationDAO;
    private FunctionResourceRelationDAO functionResourceRelationDAO;
    private IMenuFunctionRelationService menuFunctionRelationService;
    private SyncRbacCacheService syncRbacCacheService;

    public FunctionService(FunctionDAO functionDAO,
                           RoleFunctionRelationDAO roleFunctionRelationDAO,
                           FunctionPermissionRelationDAO functionPermissionRelationDAO,
                           FunctionResourceRelationDAO functionResourceRelationDAO,
                           IMenuFunctionRelationService menuFunctionRelationService,
                           SyncRbacCacheService syncRbacCacheService) {
        this.functionDAO = functionDAO;
        this.roleFunctionRelationDAO = roleFunctionRelationDAO;
        this.functionPermissionRelationDAO = functionPermissionRelationDAO;
        this.functionResourceRelationDAO = functionResourceRelationDAO;
        this.menuFunctionRelationService = menuFunctionRelationService;
        this.syncRbacCacheService = syncRbacCacheService;
    }

    /**
     * 保存功能
     *
     * @param saveDTO
     * @return
     */
    @DistributedLock(category = SecurityLockConstants.LOCK_CATEGORY_CRUD_FUNCTION, key = CommonLockConstants.LOCK_KEY_ALL)
    @Transactional(rollbackFor = Exception.class)
    public WebFunctionDTO saveFunction(WebFunctionSaveDTO saveDTO) {
        CheckUtil.notNull(saveDTO, "saveDTO null");

        String functionCode = saveDTO.getFunctionCode();
        long functionCodeCount = functionDAO.countByFunctionCode(functionCode, null);
        if (functionCodeCount > 0) {
            throw new BusinessException(SecurityMsgConstants.THE_FUNCTION_CODE_ALREADY_EXISTS,
                    NewUtil.array(functionCode));
        }

        SecFunction secFunction = new SecFunction();
        MapperUtil.mapFields(saveDTO, secFunction);
        functionDAO.save(secFunction);

        return getWebFunctionDtoById(secFunction.getId());
    }

    /**
     * 更新功能
     *
     * @param updateDTO
     * @return
     */
    @DistributedLock(category = SecurityLockConstants.LOCK_CATEGORY_CRUD_FUNCTION, key = CommonLockConstants.LOCK_KEY_ALL)
    @Transactional(rollbackFor = Exception.class)
    public WebFunctionDTO updateFunction(WebFunctionUpdateDTO updateDTO) {
        CheckUtil.notNull(updateDTO, "updateDTO null");
        Long recordId = updateDTO.getId();

        String functionCode = updateDTO.getFunctionCode();
        long functionCodeCount = functionDAO.countByFunctionCode(functionCode,
                Collections.singletonList(recordId));
        if (functionCodeCount > 0) {
            throw new BusinessException(SecurityMsgConstants.THE_FUNCTION_CODE_ALREADY_EXISTS,
                    NewUtil.array(functionCode));
        }

        SecFunction secFunction = functionDAO.getById(recordId);
        if (secFunction == null) {
            throw new DataNotFoundException(SecFunction.class, recordId);
        }

        MapperUtil.mapFields(updateDTO, secFunction);
        functionDAO.strictUpdateById(secFunction);

        return getWebFunctionDtoById(secFunction.getId());
    }

    /**
     * 删除指定 ID 的记录
     *
     * @param recordId
     */
    @DistributedLock(category = SecurityLockConstants.LOCK_CATEGORY_CRUD_FUNCTION, key = CommonLockConstants.LOCK_KEY_ALL)
    @Transactional(rollbackFor = Exception.class)
    public void deleteRecordById(Long recordId) {
        CheckUtil.notNull(recordId, "recordId null");

        functionDAO.deleteById(recordId);
        functionPermissionRelationDAO.deleteByFunctionId(recordId);
        functionResourceRelationDAO.deleteByFunctionId(recordId);
        roleFunctionRelationDAO.deleteByFunctionId(recordId);
        menuFunctionRelationService.deleteByFunctionId(recordId);

        // 同步缓存
        syncRbacCacheService.syncFunctionsWithResourceIdCache(SecFunction.class, recordId);
    }

    @Override
    public List<SecFunction> listByIds(Collection<Long> ids) {
        return functionDAO.listByIds(ids);
    }

    @Override
    public List<AssignedFunctionDTO> listAssignedFunctionsByUserId(Long userId) {
        return functionDAO.listAssignedFunctionsByUserId(userId);
    }

    /**
     * 分页查询功能
     *
     * @param queryDTO
     * @param page
     * @return
     */
    public List<WebFunctionDTO> pageWebFunctionDTOsByCond(WebFunctionQueryDTO queryDTO, Page page) {
        CheckUtil.notNull(queryDTO, "queryDTO null");
        CheckUtil.notNull(page, "page null");

        List<WebFunctionDTO> recordList = functionDAO.pageWebFunctionDTOsByCond(queryDTO, page);

        return recordList;
    }

    /**
     * 查找指定 ID 的记录
     *
     * @param id
     * @return
     */
    public WebFunctionDTO getWebFunctionDtoById(Long id) {
        SecFunction secFunction = functionDAO.getById(id);
        if (secFunction == null) {
            throw new DataNotFoundException(SecFunction.class, id);
        }

        WebFunctionDTO webFunctionDTO = FunctionConvertUtil.secFunction2WebFunctionDTO(secFunction);

        return webFunctionDTO;
    }

}
